// Written by Matthew Scholtz, copyright (c) 2007 ONE/Northwest
// This program is released under the GNU General Public License. http://www.gnu.org/licenses/

trigger ONEN_Opp_AddCampaignMembership on Opportunity (after insert, after update) {

	// when we first end up w/ a closedwon Opp that has a campaign source, 
	// ensure that the primary contact has a campaign mbrship for that campaign set to Responded
	//
	// don't worry about opp deletions - don't ever remove camp mbrship once created
	//
	// note: this doesn't prevent user from later removing camp mbr or changing it to not responded - that's OK I think
	// also note: won't trigger if user later changes primary contact on opp - that's probably OK too

	// context for bulk:
	// we may have imported a set of opps that are all for different contacts & different campaigns

	system.debug ( 'TRIGGER FIRED');

	Set<id> allOppIds = new Set<id> ();
	Map<id,id> OppToContactIds = new Map<id,id> ();
	Map<id,Map<id,CampaignMember>> ContactToCMs = new Map<id,Map<id,CampaignMember>> ();
	Map<id,Opportunity> oppsToProcess = new Map<id,Opportunity> ();
	List<CampaignMember> CMsToInsert = new List<CampaignMember> ();
	List<CampaignMember> CMsToUpdate = new List<CampaignMember> ();
	
	// use a set for this because it will automatically dedup
	Set<id> CampaignsToCheckCMS = new Set<id> ();
	
	string DefaultStatus = 'Donated';
	
	// first, make sure we have checked for contact role creation if necessary
	// (have to do this here to insure triggers happen in the right order)
	if ( Trigger.isInsert && ONEN_OppContactRoles.haveCheckedContactRoles == false ) {
		ONEN_OppContactRoles.CheckContactRoles ( trigger.newmap );	
	}
	
	// first figure out which opps need processing
	for ( Opportunity opp : Trigger.new ) {
		If ( (opp.IsWon == true && opp.IsClosed == true && opp.CampaignID != Null) && (Trigger.IsInsert || Trigger.Old[0].CampaignID != Trigger.New[0].CampaignID || Trigger.Old[0].IsWon == False )) {		
			oppsToProcess.put (opp.id, opp);	
		}
	}
	
	system.debug ('opps to process  :' + oppsToProcess.size() );
	
	If ( oppsToProcess.size() > 0 ) {
		
		system.debug ('TRIGGER WORKING..');
	
		// * get set of oppID's in trigger.new
		allOppIds = oppsToProcess.keySet();
	
		// * pass set to OppContactRoles.getPrimaryContactID, returns map PrimaryContacts (OppID -> ContactID)
		OppToContactIds = ONEN_OppContactRoles.GetPrimaryContactIdsBulk (allOppIds);
	
//		system.debug ('oppToContactIds map: ' + OppToContactIds);
	
		// * query for CampaignMbrs where ContactID in values from PrimaryContacts
		CampaignMember[] cmbrs = [Select Id, ContactId, CampaignId, Status, HasResponded From CampaignMember WHERE ContactId IN :OppToContactIds.values() ];	
	
		// * make 2-dim map CampaignMbrsForPrimaryContacts ( ContactID -> map ( CampaignID -> CampMbr obj))
		// by looping through results
		for (CampaignMember thisCM : cmbrs) {
			Map<id,CampaignMember> thisInnerMap = new Map<id,CampaignMember> ();
			thisInnerMap.put ( thisCM.CampaignID , thisCM );
			ContactToCMs.put ( thisCM.ContactID , thisInnerMap );
		}
		
//		system.debug ('contact to cm 2dim map: ' + ContactToCms );
		
		// now loop through all of our opps
		for (Opportunity thisOpp : oppsToProcess.values() ) {
			
			// * look up primary contact ID in map PrimaryContacts		
			id ConId = OppToContactIds.get(thisOpp.Id);
			
			// * look up campaignID from opp
			id CampId = thisOpp.CampaignId;
			
			// check 2nd map: do we have a CampMbr rec for that contact & campaign?
			CampaignMember thisCM;
			// need to test that we have something in the first level of the map
			// otherwise we get an error when trying to access the 2nd level
			If ( ContactToCMs.get(ConId) != null ) {thisCM = ContactToCMs.get(ConId).get(CampId);}
			
			if ( thisCM == null ) {
				// * if not: construct new CM w/ proper ConID and CampID & status, and add to list
				
				//but only if we have a ConId (i.e. there was a primary ConRole for the opp)
				//(if not, do nothing)
				// TBD should really be testing for this further up and bailing sooner
				
				if ( ConId != null ) {
					CampaignMember newCM = new CampaignMember(
						ContactId = ConId ,
						CampaignId = CampId ,
						Status = DefaultStatus
					);
					CMsToInsert.add (newCM);
					
					// also put campaign on the list to check for proper status
					CampaignsToCheckCMS.add (CampId);
				}
				
			} else {
				// * if so: is it Responded?
				if ( thisCM.HasResponded == true ) {
					// * if so: do nothing
				} else {
					// * if not: add to list of CampMbr objects NeedsUpdateToResponded
					thisCM.Status = DefaultStatus;
					//thisCM.Status = 'Responded';
					CMsToUpdate.add(thisCM);
					
					// also put campaign on the list to check for proper status
					CampaignsToCheckCMS.add (thisCM.CampaignId);
					
				}
			}
			
		}
		
		// check list of Campaigns that they have the Donated status, add if not
		// last param means we require that the status be HasResponded, and change it to that if it's not 
		// already
		
		boolean statusOK = ONEN_CampaignMbrStatus.CheckCMStatusExistsBulk (CampaignsToCheckCMS,DefaultStatus,true);
		
		//update and insert
		
//		system.debug ('to update: ' + CMsToUpdate);
//		system.debug ('to insert: ' + CMsToInsert);
		
		if ( CMsToUpdate.size() > 0 ) {
			try {
				update CMsToUpdate;		
			} catch (System.DmlException e) {
				system.debug ('error: ' + e );	
			}
		}
		
		insert CMsToInsert;
			
	} //if

/* original trigger (not bulk safe):
	for (Opportunity opp : Trigger.new) {

		If ( (opp.IsWon == True && opp.CampaignID != Null) && (Trigger.IsInsert || Trigger.Old[0].CampaignID != Trigger.New[0].CampaignID || Trigger.Old[0].IsWon == False )) {
			
			Id campId = opp.CampaignID;
			Id oppId = opp.Id;
			string newStatus;
			
			// call class method to get Id of primary contact (if any)
			Id conRoleId = OppContactRoles.GetPrimaryContactId (oppId);
			
			// if we have a primary contact..
			If ( conRoleId != Null ) {

				// check to see if they're already part of the campaign
				CampaignMember[] cmbrs = [Select c.Id, c.Status, c.HasResponded From CampaignMember c WHERE c.ContactId = :conRoleId AND c.CampaignId = :campId];	
				
				// first make sure that we have a status of Donated for this campaign that is Responded
				newStatus = 'Donated';
				boolean stExists = CampaignMbrStatus.CheckCMStatusExists (campId , newStatus, True);	
								
				If ( cmbrs.size() > 0 ) {
					// if already in campaign, chg status to responded if not already so 
					If ( cmbrs[0].HasResponded != True ) {
						CampaignMember newCmbr = New CampaignMember (
							Id = cmbrs[0].Id,
							Status = newStatus
						);
						
						update newCmbr;
					}
					
				} Else {
					// if not yet a mbr of this campaign, add them to it as Responded

					// add the new campmbr rec
					CampaignMember newCmbr = New CampaignMember (
						ContactId = conRoleId,
						CampaignId = campId,
						Status = newStatus
					);
					
					insert newCmbr;
				}
				
			}
		
		}
	}	   
*/
	
}